package com.xhs.service;

import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.LoadState;
import com.microsoft.playwright.options.WaitForSelectorState;
import com.xhs.config.PlaywrightConfig;
import com.xhs.config.ProxyConfig;
import com.xhs.dto.XhsApiResponse;
import com.xhs.dto.BrowserSession;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Playwright服务类
 *
 * @author xhs
 */
@Slf4j
@Service
public class PlaywrightService {

    @Autowired
    private PlaywrightConfig config;

    @Autowired
    private UserService userService;

    private Playwright playwright;
    private BrowserType browserType;
    private final Map<String, BrowserSession> activeSessions = new ConcurrentHashMap<>();

    private static final String XHS_URL = "https://www.xiaohongshu.com";

    @PostConstruct
    public void init() {
        try {
            // 确保控制台编码正确
            System.setProperty("file.encoding", "UTF-8");
            System.setProperty("console.encoding", "UTF-8");
            
            // 设置Playwright浏览器路径 - 仅设置浏览器目录，不设置CLI目录
            String browsersPath = System.getProperty("PLAYWRIGHT_BROWSERS_PATH", "data/.ms-playwright");
            System.setProperty("PLAYWRIGHT_BROWSERS_PATH", browsersPath);
            
            // 重要：不要设置playwright.cli.dir，让Playwright使用内置驱动
            // 删除CLI目录设置，这是导致找不到playwright.cmd的原因
            
            // 检查浏览器目录是否存在
            java.io.File browserDir = new java.io.File(browsersPath);
            if (browserDir.exists()) {
                log.info("Browser directory found: {}", browserDir.getAbsolutePath());
                java.io.File[] files = browserDir.listFiles();
                if (files != null) {
                    for (java.io.File file : files) {
                        log.info("Browser file: {}", file.getName());
                    }
                }
            } else {
                log.warn("Browser directory not found: {}", browserDir.getAbsolutePath());
            }
            
            // 禁用浏览器下载，使用预安装的浏览器
            System.setProperty("playwright.skip.browser.download", "false");
            
            log.info("Playwright browser path set to: {}", browsersPath);
            log.info("Creating Playwright instance...");
            
            playwright = Playwright.create();
            browserType = playwright.chromium();

            log.info("Playwright service initialized successfully");
        } catch (Exception e) {
            log.error("Failed to initialize Playwright service", e);
            throw new RuntimeException("Playwright initialization failed", e);
        }
    }

    @PreDestroy
    public void destroy() {
        // 关闭所有活跃会话
        activeSessions.values().forEach(BrowserSession::close);
        activeSessions.clear();

        if (playwright != null) {
            playwright.close();
        }

        log.info("Playwright服务销毁完成");
    }

    /**
     * 输入手机号
     */
    public Map<String, Object> inputPhoneNumber(String phoneNumber) {
        // 检查是否已存在该手机号的会话
        BrowserSession existingSession = getSessionByPhoneNumber(phoneNumber);
        if (existingSession != null && !existingSession.getPage().isClosed()) {
            // 复用现有会话
            log.info("复用现有会话，手机号: {}, 会话ID: {}", phoneNumber, existingSession.getSessionId());
            existingSession.updateLastActiveTime();

            Map<String, Object> result = new HashMap<>();
            result.put("sessionId", existingSession.getSessionId());
            result.put("phoneNumber", phoneNumber);
            result.put("status", "PHONE_INPUT_SUCCESS");
            result.put("message", "手机号输入成功，请输入验证码");
            result.put("isReused", true);

            return result;
        }

        // 创建新会话
        String sessionId = IdUtil.simpleUUID();
        BrowserSession session = new BrowserSession(sessionId, phoneNumber);

        // 创建浏览器实例
        BrowserType.LaunchOptions launchOptions = createLaunchOptions();
        
        Browser browser = browserType.launch(launchOptions);

        // 创建浏览器上下文，设置用户代理和视口（使用代理）
        Browser.NewContextOptions contextOptions = createContextOptions();
        
        BrowserContext context = browser.newContext(contextOptions);
        Page page = context.newPage();
        
        // 设置页面超时
        page.setDefaultTimeout(config.getPageTimeout()); // 使用配置的超时时间

        session.setBrowser(browser);
        session.setContext(context);
        session.setPage(page);

        // 导航到小红书官网
        page.navigate(XHS_URL);

        // 等待页面加载
        page.waitForTimeout(1000);

        // 点击登录按钮
        clickLoginButton(page);

        // 输入手机号
        inputPhone(page, phoneNumber);

        // 重试3次发送验证码
        final int MAX_RETRIES = 3;
        String sendCodeResponse = "";
        long waitTime = getWaitTimeForAction();
        for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) {
            try {
                sendCodeResponse = clickSendCodeAndCapture(page, phoneNumber);
                XhsApiResponse<?> xhsResponse = JSONUtil.toBean(sendCodeResponse, XhsApiResponse.class);
                if (xhsResponse.isSuccess()) {
                    log.info("发送验证码成功，第{}次尝试", attempt);
                    break;
                } else {
                    throw new RuntimeException("小红书API返回错误: " + xhsResponse.getErrorMessage());
                }
            } catch (Exception e) {
                log.warn("发送验证码第{}次尝试失败: {}", attempt, e.getMessage());
                if (attempt == MAX_RETRIES) {
                    log.error("重试{}次后发送验证码依然失败", MAX_RETRIES);
                    closeSession(sessionId);
                    throw new RuntimeException("发送验证码失败: " + e.getMessage());
                }
                page.waitForTimeout(waitTime);
            }
        }

        session.setStatus(BrowserSession.SessionStatus.PHONE_INPUT);

        // 保存到内存
        activeSessions.put(sessionId, session);

        log.info("手机号输入完成，会话ID: {}, 手机号: {}", sessionId, phoneNumber);

        Map<String, Object> result = new HashMap<>();
        result.put("sessionId", sessionId);
        result.put("phoneNumber", phoneNumber);
        result.put("status", "PHONE_INPUT_SUCCESS");
        result.put("message", "手机号输入成功，请输入验证码");
        result.put("sendCodeResponse", sendCodeResponse);
        result.put("isReused", false);

        return result;
    }

    /**
     * 验证码登录
     */
    public String verifyLogin(String phoneNumber, String verifyCode) {
        // 根据手机号查找会话
        BrowserSession session = getSessionByPhoneNumber(phoneNumber);

        if (session == null || session.getPage().isClosed()) {
            throw new RuntimeException("未找到对应的会话，请先输入手机号");
        }

        Page page = session.getPage();

        // 重试3次进行验证码登录
        final int MAX_RETRIES = 3;
        long waitTime = getWaitTimeForAction();
        String checkCodeResponse = "";
        for (int attempt = 1; attempt <= MAX_RETRIES; attempt++) {
            try {
                attempt++;
                // 输入验证码
                inputVerifyCode(page, verifyCode);

                // 监听验证码核对接口并点击登录按钮
                checkCodeResponse = clickLoginAndMonitorCheckCode(page, phoneNumber, verifyCode);

                // 检查验证码验证的响应
                if (checkCodeResponse == null || checkCodeResponse.isEmpty()) {
                    throw new RuntimeException("未获取到验证码验证响应");
                }

                XhsApiResponse<?> xhsResponse = JSONUtil.toBean(checkCodeResponse, XhsApiResponse.class);
                if (!xhsResponse.isSuccess()) {
                    throw new RuntimeException("小红书API返回错误: " + xhsResponse.getErrorMessage());
                }

                // 登录成功
                List<Map<String, Object>> cookies = getCookiesFromPage(page);
                userService.loginWithCookies(phoneNumber, cookies);
                log.info("用户登录信息已保存: phoneNumber={}", phoneNumber);

                session.setStatus(BrowserSession.SessionStatus.LOGIN_SUCCESS);
                session.updateLastActiveTime();

                log.info("登录成功，会话ID: {}, 手机号: {}, cookies已保存到MySQL", session.getSessionId(), phoneNumber);
                closeSession(session.getSessionId());
                return checkCodeResponse;
            } catch (Exception e) {
                session.setStatus(BrowserSession.SessionStatus.LOGIN_FAILED);
                log.warn("登录第{}次尝试失败: {}", attempt, e.getMessage());
                if (attempt == MAX_RETRIES) {
                    log.error("重试{}次后登录依然失败", MAX_RETRIES);
                    closeSession(session.getSessionId());
                    throw new RuntimeException("登录失败: " + e.getMessage());
                }
                page.waitForTimeout(waitTime);
            }
        }
        // 理论上不会执行到这里
        return checkCodeResponse;
    }

    /**
     * 点击登录按钮
     */
    private void clickLoginButton(Page page) {
        long timeout = getTimeoutForElement();
        long waitTime = getWaitTimeForAction();
        
        // 多种登录按钮选择器
        String[] loginSelectors = {
            "text=登录",
            "button:has-text('登录')",
            "a:has-text('登录')",
            ".login-btn",
            ".login-button",
            "[data-testid='login-button']",
            "[data-testid='login-btn']",
            "button[type='submit']",
            ".btn-login"
        };

        boolean clickSuccess = false;
        
        // 如果是headless模式，先等待页面稳定
        if (config.getHeadless()) {
            log.info("Headless模式，等待页面稳定...");
            page.waitForTimeout(waitTime);
        }

        // 尝试多种选择器
        for (String selector : loginSelectors) {
            try {
                log.debug("尝试登录按钮选择器: {}", selector);
                page.waitForSelector(selector, new Page.WaitForSelectorOptions()
                    .setTimeout(timeout)
                    .setState(WaitForSelectorState.VISIBLE));
                
                // 确保按钮可点击
                page.waitForSelector(selector, new Page.WaitForSelectorOptions()
                    .setTimeout(timeout)
                    .setState(WaitForSelectorState.VISIBLE));
                
                page.click(selector);
                log.info("点击登录按钮成功，使用选择器: {}", selector);
                clickSuccess = true;
                break;
            } catch (Exception e) {
                log.debug("登录按钮选择器 {} 失败: {}", selector, e.getMessage());
            }
        }

        if (!clickSuccess) {
            log.error("所有登录按钮选择器都失败");
            throw new RuntimeException("无法找到登录按钮");
        }

        // 等待登录弹窗出现
        page.waitForTimeout(waitTime);
    }

    /**
     * 输入手机号
     */
    private void inputPhone(Page page, String phoneNumber) {
        // headless模式下需要更长的等待时间
        long timeout = getTimeoutForElement();
        long waitTime = getWaitTimeForAction();
        
        // 多种选择器策略
        String[] phoneSelectors = {
            "input[placeholder*='手机号']",
            "input[placeholder*='请输入手机号']",
            "input[placeholder*='手机号码']",
            "input[type='tel']",
            "input[name='phone']",
            "input[name='mobile']",
            "input[type='text']",
            ".phone-input",
            ".mobile-input",
            "[data-testid='phone-input']",
            "[data-testid='mobile-input']"
        };

        boolean inputSuccess = false;
        
        // 如果是headless模式，先等待页面完全加载
        if (config.getHeadless()) {
            log.info("Headless模式，等待页面完全加载...");
            page.waitForTimeout(waitTime);
            
            // 等待网络空闲
            try {
                page.waitForLoadState(LoadState.NETWORKIDLE, new Page.WaitForLoadStateOptions().setTimeout(timeout));
            } catch (Exception e) {
                log.warn("等待网络空闲超时，继续执行", e);
            }
        }

        // 尝试多种选择器
        for (String selector : phoneSelectors) {
            try {
                log.debug("尝试选择器: {}", selector);
                page.waitForSelector(selector, new Page.WaitForSelectorOptions().setTimeout(timeout));
                
                // 确保元素可见
                page.waitForSelector(selector, new Page.WaitForSelectorOptions()
                    .setTimeout(timeout)
                    .setState(WaitForSelectorState.VISIBLE));
                
                // 清空输入框并输入手机号
                page.fill(selector, "");
                page.fill(selector, phoneNumber);
                
                // 验证输入是否成功
                String inputValue = page.inputValue(selector);
                // 移除空格进行比较，因为页面可能会自动格式化手机号
                String normalizedInput = inputValue.replaceAll("\\s+", "");
                String normalizedExpected = phoneNumber.replaceAll("\\s+", "");
                
                if (normalizedExpected.equals(normalizedInput)) {
                    log.info("输入手机号成功: {}, 使用选择器: {}, 页面显示: {}", phoneNumber, selector, inputValue);
                    inputSuccess = true;
                    break;
                } else {
                    log.warn("输入验证失败，期望: {}, 实际: {}", phoneNumber, inputValue);
                }
            } catch (Exception e) {
                log.debug("选择器 {} 失败: {}", selector, e.getMessage());
            }
        }

        if (!inputSuccess) {
            log.error("所有手机号输入框选择器都失败");
            throw new RuntimeException("无法找到手机号输入框");
        }

        // 等待输入完成
        page.waitForTimeout(waitTime);
    }

    /**
     * 点击发送验证码并捕获响应
     */
    private String clickSendCodeAndCapture(Page page, String phoneNumber) {
        AtomicReference<String> responseContent = new AtomicReference<>("");

        // 监听网络请求
        page.onResponse(response -> {
            String url = response.url();
            if (url.contains("send_code") && url.contains("xiaohongshu.com")) {
                try {
                    String body = response.text();
                    responseContent.set(body);
                    log.info("捕获到发送验证码响应: {}", body);
                } catch (Exception e) {
                    log.error("获取响应内容失败", e);
                }
            }
        });

        // 点击获取验证码按钮
        String[] sendCodeSelectors = {
                "text=获取验证码",
                "button:has-text('获取验证码')",
                "text=发送验证码",
                "button:has-text('发送验证码')",
                "text=获取短信验证码",
                "button:has-text('获取短信验证码')",
                ".get-code-btn",
                ".send-code-btn",
                ".sms-code-btn",
                "[data-testid='get-code-button']",
                "[data-testid='send-code-button']",
                "[data-testid='sms-code-button']",
                ".verify-code-btn",
                ".code-button"
        };

        boolean clicked = false;
        long timeout = getTimeoutForElement();
        long waitTime = getWaitTimeForAction();
        
        // headless模式下先等待页面稳定
        if (config.getHeadless()) {
            log.info("Headless模式，等待发送验证码按钮加载...");
            page.waitForTimeout(waitTime);
        }
        
        for (String selector : sendCodeSelectors) {
            try {
                log.debug("尝试发送验证码按钮选择器: {}", selector);
                page.waitForSelector(selector, new Page.WaitForSelectorOptions()
                    .setTimeout(timeout)
                    .setState(WaitForSelectorState.VISIBLE));
                
                // 确保按钮可点击
                page.waitForSelector(selector, new Page.WaitForSelectorOptions()
                    .setTimeout(timeout)
                    .setState(WaitForSelectorState.VISIBLE));
                
                page.click(selector);
                clicked = true;
                log.info("成功点击发送验证码按钮: {}", selector);
                break;
            } catch (Exception e) {
                log.debug("尝试点击选择器 {} 失败: {}", selector, e.getMessage());
            }
        }

        if (!clicked) {
            log.warn("未找到发送验证码按钮，可能需要手动点击");
        }

        // 等待响应
        page.waitForTimeout(waitTime);

        return responseContent.get();
    }

    /**
     * 输入验证码
     */
    private void inputVerifyCode(Page page, String verifyCode) {
        // 等待验证码输入框出现
        try {
            page.waitForSelector("input[placeholder*='验证码']", new Page.WaitForSelectorOptions().setTimeout(15000));
            page.fill("input[placeholder*='验证码']", verifyCode);
            log.info("输入验证码成功");
        } catch (Exception e) {
            log.warn("使用placeholder选择器失败，尝试其他选择器", e);
            // 尝试其他可能的选择器
            try {
                page.waitForSelector("input[type='text']:not([placeholder*='手机号'])", new Page.WaitForSelectorOptions().setTimeout(10000));
                page.fill("input[type='text']:not([placeholder*='手机号'])", verifyCode);
                log.info("使用备用选择器输入验证码成功");
            } catch (Exception e2) {
                log.error("所有验证码输入框选择器都失败", e2);
                throw new RuntimeException("无法找到验证码输入框");
            }
        }

        // 等待输入完成
        page.waitForTimeout(1500);
    }

    /**
     * 点击登录并监听验证码核对接口
     */
    private String clickLoginAndMonitorCheckCode(Page page, String phoneNumber, String verifyCode) {
        AtomicReference<String> checkCodeResponse = new AtomicReference<>("");

        // 监听验证码核对接口
        page.onResponse(response -> {
            String url = response.url();
            if (url.contains("check_code") && url.contains("xiaohongshu.com")) {
                try {
                    String body = response.text();
                    checkCodeResponse.set(body);
                    log.info("捕获到验证码核对响应: {}", body);
                } catch (Exception e) {
                    log.error("获取验证码核对响应失败", e);
                }
            }
        });

        // 点击登录按钮
        page.click("button:has-text('登录')");

        // 等待登录处理
        page.waitForTimeout(1000);

        // 检查是否出现同意协议弹窗
        handleAgreementPopup(page);

        // 等待响应
        page.waitForTimeout(1000);

        return checkCodeResponse.get();
    }

    /**
     * 从页面获取 cookies
     */
    private List<Map<String, Object>> getCookiesFromPage(Page page) {
        try {
            List<com.microsoft.playwright.options.Cookie> playwrightCookies = page.context().cookies();
            List<Map<String, Object>> cookies = new ArrayList<>();

            for (com.microsoft.playwright.options.Cookie cookie : playwrightCookies) {
                Map<String, Object> cookieMap = new HashMap<>();
                cookieMap.put("name", cookie.name);
                cookieMap.put("value", cookie.value);
                cookieMap.put("domain", cookie.domain);
                cookieMap.put("path", cookie.path);
                cookieMap.put("expires", cookie.expires);
                cookieMap.put("httpOnly", cookie.httpOnly);
                cookieMap.put("secure", cookie.secure);
                cookieMap.put("sameSite", cookie.sameSite);
                cookies.add(cookieMap);
            }

            log.info("获取到 {} 个 cookies", cookies.size());
            return cookies;
        } catch (Exception e) {
            log.error("获取 cookies 失败", e);
            return new ArrayList<>();
        }
    }

    /**
     * 处理同意协议弹窗
     */
    private void handleAgreementPopup(Page page) {
        try {
            // 检查是否有同意协议弹窗
            String[] popupSelectors = {
                    "text=同意并继续",
                    "button:has-text('同意并继续')",
                    "text=同意",
                    "button:has-text('同意')",
                    ".agreement-popup button",
                    "[data-testid='agreement-continue']"
            };

            for (String selector : popupSelectors) {
                try {
                    Locator button = page.locator(selector).first();
                    if (button.isVisible()) {
                        button.click();
                        log.info("成功点击同意协议弹窗按钮: {}", selector);
                        page.waitForTimeout(1000);
                        break;
                    }
                } catch (Exception e) {
                    log.debug("尝试点击协议弹窗按钮 {} 失败: {}", selector, e.getMessage());
                }
            }
        } catch (Exception e) {
            log.error("处理同意协议弹窗失败", e);
        }
    }

    /**
     * 根据手机号获取会话ID
     */
    public String getSessionIdByPhoneNumber(String phoneNumber) {
        return activeSessions.values().stream()
                .filter(session -> phoneNumber.equals(session.getPhoneNumber()))
                .findFirst()
                .map(BrowserSession::getSessionId)
                .orElse("unknown");
    }

    /**
     * 根据手机号获取会话
     */
    public BrowserSession getSessionByPhoneNumber(String phoneNumber) {
        return activeSessions.values().stream()
                .filter(session -> phoneNumber.equals(session.getPhoneNumber()))
                .findFirst()
                .orElse(null);
    }

    /**
     * 获取所有活跃会话
     */
    public Map<String, BrowserSession> getActiveSessions() {
        return new HashMap<>(activeSessions);
    }

    /**
     * 关闭指定会话
     */
    public void closeSession(String sessionId) {
        BrowserSession session = activeSessions.get(sessionId);
        if (session != null) {
            session.close();
            activeSessions.remove(sessionId);
            log.info("会话已关闭: sessionId={}, phoneNumber={}", sessionId, session.getPhoneNumber());
        } else {
            log.warn("未找到要关闭的会话: sessionId={}", sessionId);
        }
    }

    /**
     * 关闭手机号对应的会话
     */
    public void closeSessionByPhoneNumber(String phoneNumber) {
        BrowserSession session = getSessionByPhoneNumber(phoneNumber);
        if (session != null) {
            closeSession(session.getSessionId());
        }
    }

    /**
     * 根据headless模式获取适当的超时时间
     */
    private long getTimeoutForElement() {
        // 非headless模式使用更短的超时时间
        return config.getHeadless() ? config.getElementTimeout() : Math.min(config.getElementTimeout(), 5000L);
    }

    /**
     * 根据headless模式获取适当的等待时间
     */
    private long getWaitTimeForAction() {
        // 非headless模式使用更短的等待时间
        return config.getHeadless() ? config.getHeadlessActionWaitTime() : Math.min(config.getActionWaitTime(), 800L);
    }

    /**
     * 创建适合headless模式的浏览器选项
     */
    private BrowserType.LaunchOptions createLaunchOptions() {
        BrowserType.LaunchOptions options = new BrowserType.LaunchOptions()
                .setHeadless(config.getHeadless());
        
        // 配置代理（在浏览器启动时配置）
        if (config.getProxyConfig() != null && config.getProxyConfig().isConfigured()) {
            String proxyUrl = config.getProxyConfig().getProxyUrl();
            options.setProxy(proxyUrl);
            
            if (hasProxyCredentials(config.getProxyConfig())) {
                log.info("Playwright代理已配置（带认证）: {}", proxyUrl);
            } else {
                log.info("Playwright代理已配置（白名单模式）: {}", proxyUrl);
            }
        }
        
        if (config.getHeadless()) {
            // headless模式下的特殊配置
            options.setArgs(config.getHeadlessArgs());
        } else {
            // 非headless模式下设置慢速模式
            options.setSlowMo(config.getSlowMo());
        }
        
        return options;
    }

    /**
     * 创建浏览器上下文选项（不带代理）
     */
    private Browser.NewContextOptions createContextOptions() {
        return createContextOptions(false);
    }

    /**
     * 创建浏览器上下文选项
     * @param useProxy 是否使用代理（已废弃，代理现在在启动时配置）
     */
    private Browser.NewContextOptions createContextOptions(boolean useProxy) {
        Browser.NewContextOptions options = new Browser.NewContextOptions()
                .setUserAgent(config.getUserAgent())
                .setViewportSize(config.getViewportWidth(), config.getViewportHeight())
                .setExtraHTTPHeaders(config.getExtraHttpHeaders());
        
        // 设置代理认证（如果有的话）
        if (config.getProxyConfig() != null && config.getProxyConfig().isConfigured() && hasProxyCredentials(config.getProxyConfig())) {
            options.setHttpCredentials(config.getProxyConfig().getUsername(), config.getProxyConfig().getPassword());
        }
        
        if (config.getHeadless()) {
            // headless模式下的特殊配置
            options.setIgnoreHTTPSErrors(config.getIgnoreHttpsErrors())
                   .setJavaScriptEnabled(config.getHeadlessJavaScriptEnabled());
        }
        
        return options;
    }

    /**
     * 检查是否有代理认证信息
     */
    private boolean hasProxyCredentials(ProxyConfig proxyConfig) {
        return proxyConfig.getUsername() != null && !proxyConfig.getUsername().trim().isEmpty() &&
               proxyConfig.getPassword() != null && !proxyConfig.getPassword().trim().isEmpty();
    }


} 